#include "TextLine.h"
#include <string.h> 

CTextLine::CTextLine () : CVarChars(LINE_INIT_LEN)
{
    memset (m_pArray, 0, sizeof(char) * m_nCount);
}

CTextLine::~CTextLine()
{
}

unsigned int CTextLine::InsertTextAt(unsigned int idx, const char* text)
{
    unsigned int curlen = strlen(m_pArray);
    unsigned int textlen = strlen(text);

    assert (!InsideMultiByteChar(m_pArray, idx));
    assert (idx <= curlen);
    unsigned int totallen = curlen + textlen + 1;
    if (totallen > m_nCount) {
        Resize ((totallen/LEN_GROW_STEP + 1) * LEN_GROW_STEP); 
    }
    InsertAt(idx, textlen, text);
    return (curlen +textlen);
}

unsigned int CTextLine::RemoveText (unsigned int start, unsigned int end)
{
    assert (m_pArray);
    unsigned int len = strlen(m_pArray);
    if (start >= len && end >= len) {
        /* so nothing to remove. */
        return len;
    }

    end = (end == EOL ? strlen(m_pArray)-1 : end);
    if (start > end) {
        int tmp = end;
        end = start;
        start = tmp;
    }

    assert ((unsigned int)start <= strlen(m_pArray));
    assert ((unsigned int)end <= strlen(m_pArray));

    assert (!InsideMultiByteChar(m_pArray, start));
    assert (!InsideMultiByteChar(m_pArray, end+1));

    memmove (m_pArray+start, m_pArray+end+1, m_nCount - end -1);
    unsigned int totallen = strlen(m_pArray) + 1;
    if (m_nCount - totallen > LEN_GROW_STEP ) {
        Resize ((totallen/LEN_GROW_STEP + 1) * LEN_GROW_STEP); 
    }

    return strlen(m_pArray);
}

unsigned int CTextLine::GetNextCharacter ( unsigned int start, unsigned long* character, int* len)
{
    return GetMultiByteChar(m_pArray, start, character, len);
}

unsigned int CTextLine::DisplayColToIndex (unsigned int col, unsigned int tabsize, bool rightalign/*=false*/)
{
    assert (m_pArray);
    assert (tabsize);

    unsigned int idx = 0;
    unsigned int col_calc = 0;
    unsigned int len = strlen(m_pArray);

    while (idx <= len && col_calc < col) {
        col_calc = ((m_pArray[idx] == '\t') ? (col_calc+tabsize)/ tabsize * tabsize: col_calc +1);
        idx++;
    }
    if (col_calc > col) {
        (rightalign ? idx :  --idx);
    }
    while (InsideMultiByteChar(m_pArray, idx) && idx != 0) {
        (rightalign ? ++idx :  --idx);
    }

    return idx;
}

unsigned int CTextLine::IndexToDisplayCol (unsigned int idx, unsigned int tabsize)
{
    assert (m_pArray);
    assert (tabsize);

    unsigned int i = 0;
    unsigned int col = 0;

    if (idx == EOL) {
        idx = strlen (m_pArray);
    }
    while (i < idx) {
        col = (m_pArray[i] == '\t' ? (col+tabsize)/ tabsize * tabsize: col+1);
        i++;
    }
    return col;
}

unsigned int CTextLine::GetTotalCols(unsigned int tabsize)
{
    assert (m_pArray);
    assert (tabsize);

    unsigned int idx = 0;
    unsigned int col = 0;
    
    while (m_pArray[idx] != '\0') {
        col = (m_pArray[idx] == '\t' ? (col+tabsize)/ tabsize * tabsize: col+1); 
        idx++;
    }
    return col;
}

unsigned int CTextLine::GetIdxForRemoveLeadingSpacesBefore (unsigned int idx)
{
    int i;
    for (i = 0; (unsigned int)i < idx && (unsigned int)i< strlen(m_pArray); i++) {
        if (m_pArray[i] != ' ' && m_pArray[i] != '\t') {
            break;
        }
    }
    i--;    
    if (i < 0) {
        return EOL;
    }
    return (unsigned int) i;
}

void CTextLine::RemoveLeadingSpacesBefore(unsigned int idx)
{
    unsigned int uToBeRemoved = GetIdxForRemoveLeadingSpacesBefore(idx);
    if (idx != EOL) {
        RemoveText(0, uToBeRemoved);
    }
}

/* FindInLineForward searchs str in this line starting from start and ending at end.
 * If found, returns true and found_idx is updated to the start index of the found.
 * [Start, end)
 */
bool CTextLine::FindInLineForward (unsigned int start, unsigned int end, const char* str, unsigned int* found_idx)
{
    assert (m_pArray);
    assert (found_idx);

    if (!str || str[0] == '\0') {
        return false;
    }

    size_t len = strlen(m_pArray);
    size_t search_len = strlen(str);

    start = (start == EOL ? len : start);
    end = (end == EOL ? len : end);
    assert (start <= end);
    
    if (end < search_len || end - search_len < start) {
        /* selection is shorter then the length of str.*/
        return false;
    }

    while (start <= end - search_len) {
        assert (m_pArray[start] != '\0');
        if (strncmp(m_pArray+start, str, search_len) == 0) {
            *found_idx = start;
            return true;
        }
        start++;
    }
    return false;
}

/* FindInLineBackward searchs str in this line starting from start and ending at end.
 * Searching starts from end to start, backward.
 * If found, returns true and found_idx is updated to the start index of the found.
 * [Start, end)
 */
bool CTextLine::FindInLineBackward (unsigned int start, unsigned int end, const char* str, unsigned int* found_idx)
{
    assert (m_pArray);
    assert (found_idx);

    if (!str || str[0] == '\0') {
        return false;
    }

    size_t len = strlen(m_pArray);
    size_t search_len = strlen(str);

    start = (start == EOL ? len : start);
    end = (end == EOL ? len : end);
    assert (start <= end);
    
    if (end < search_len || end - search_len < start) {
        /* selection is shorter then the length of str.*/
        return false;
    }

    end -= search_len;
    while (start <= end) {
        assert (m_pArray[end] != '\0');
        if (strncmp(m_pArray+end, str, search_len) == 0) {
            *found_idx = end;
            return true;
        }
        if (end > 0) {
            end--;
        } else {
            break;
        }
    }
    return false;
}

#define IS_SPACE(c) (c == ' ' || c == '\t')
char* CTextLine::GetLeadingSpaces()
{
    char* spaces = NULL;
    unsigned int len = 0;
    
    while (IS_SPACE(m_pArray[len]) ) {
        len++;
    }
    if (!len) {
        return NULL;
    }
    spaces = (char*) malloc (len+1);
    if (!spaces) {
        return NULL;
    }

    strncpy (spaces, m_pArray, len);
    spaces[len] = '\0';
    return spaces;
}

#define IS_NUMERIC(c) (c >= '0' && c <='9')
#define IS_ALPHABETIC(c) ((c >= 'a' && c <='z') || (c >= 'A' && c <= 'Z'))
#define IS_ID_CHAR(c) (IS_NUMERIC(c) || IS_ALPHABETIC(c) || c == '_')

char* CTextLine::GetLastToken()
{
    size_t len = strlen(m_pArray);
    if (!len) {
        return NULL;
    }

    size_t idx = len - 1;
    while (idx >= 0 && IS_SPACE(m_pArray[idx])) {
        idx --;
    }
    unsigned int end = idx;
    unsigned int start;
    if (idx == 0) {
        start = 0;
    } else {
        while (idx > 0 && IS_ID_CHAR(m_pArray[idx])) {
            idx --;
        }
        start = idx;
        if (!IS_ID_CHAR(m_pArray[start]) && IS_ID_CHAR(m_pArray[end])) {
            start = idx+1;
        }
    }

    char* token = NULL;
    token = (char*) malloc (end-start +2);
    if (!token) {
        return NULL;
    }
    strncpy (token, m_pArray+start, end-start+1);
    token[end-start+1] = '\0';
    return token;
}

char* CTextLine::GetFirstTokenAndPos(unsigned int* start)
{
    unsigned int start_pos = 0;
    while (IS_SPACE(m_pArray[start_pos])) {
        start_pos++;
    }
    if (start) {
        *start = start_pos;
    }
    if (m_pArray[start_pos] == '\0') {
        return NULL;
    }
    
    unsigned int token_len = 0;
    while (IS_ID_CHAR(m_pArray[start_pos+token_len])) {
        token_len ++;
    }
    if (token_len == 0) {
        // the 1st char id not ID char, so, len = 1
        token_len = 1;
    }
    char* token = NULL;
    token = (char*) malloc (token_len+1);
    if (!token) {
        return NULL;
    }
    token[token_len] = '\0';
    memcpy (token, m_pArray+start_pos, token_len);
    return token;
}

unsigned int CTextLine::GetTokenByIndex(unsigned int idx, unsigned int* start)
{
    assert (start);
    unsigned int left, right;
    unsigned int len = 0;

    if (idx > strlen (m_pArray)) {
        return 0;
    }
    
    if (InsideMultiByteChar(m_pArray, idx)) {
        // only supports 2 bytes Char. 4 bytes are not supported.
        idx = (idx > 0 ? idx-1 : 0);
    }
    if (IS_LEADING_CHAR(m_pArray[idx])) {
        *start = idx;
        return 2;
    }
    // look right
    right = idx;
    while (IS_ID_CHAR(m_pArray[right])) {
        right ++;
    }
    if (right == idx && !IS_SPACE(m_pArray[idx]) && m_pArray[idx] != '\0') {
        /* the next char is not ID char,
         */
        *start = idx;
        return 1;
    }

    len = right - idx;
    // look left
    left = idx;
    while (left > 0 && IS_ID_CHAR(m_pArray[left-1]) ) {
        left--;
        len ++;
    }
    *start = left;
    return len;
}

unsigned int GetMultiByteChar(const char* text, unsigned int start, 
                     unsigned long* character, int* len)
{
    /* GB18030:
     * 1 byte char: [0x00, 0x7F]
     * 2 byte char: leading: [0x81, 0xFE], trailing: [0x40, 0x7E] or [0x80, 0xFE]
     * 4 byte char:
     *     1st byte: [0x81, 0xFE]
     *     2nd byte: [0x30, 0x39]
     *     3rd byte: [0x81, 0xFE]
     *     4th byte: [0x30, 0x39]
     */
    assert (text);
    assert (start <= strlen(text));
    assert (character);
    assert (len);

    unsigned int textlen = strlen(text);
    unsigned char* c = (unsigned char*)character;
    
    if (start >= textlen) {
        *c = 0;
        *len = 1;
        return EOL;
    }

    if (!IS_LEADING_CHAR(text[start])) {
        /* It can be either:
         *  a) ASCII or
         *  b) unrecognized char
         * In both cases, it returns one byte.
         */
        *len = 1;
        *c = text[start];
        ++start;
        assert (start <= textlen);
        return start;
    }
    /* Now, it's the leading character.*/
    if (start+1 == textlen) {
        /* trailing character is missing.*/
        *len = 1;
        *c = text[start];
        return textlen;
    }
    
    if (IS_TRAILING_CHAR(text[start+1])) {
        /* it's a double byte character*/
        *len = 2;
        *c = text[start];
        *(c+1) = text[start+1];
        assert (start+2 <= textlen);
        return (start+2);
    }
    /* 4 bytes not supported.*/
    *len = 1;
    *c = text[start];
    ++start;
    assert (start <=textlen);
    return start;
}

bool InsideMultiByteChar(const char* text, unsigned int idx)
{
    /* GB18030:
     * 1 byte char: [0x00, 0x7F]
     * 2 byte char: leading: [0x81, 0xFE], trailing: [0x40, 0x7E] or [0x80, 0xFE]
     * 4 byte char: (not supported)
     *     1st byte: [0x81, 0xFE]
     *     2nd byte: [0x30, 0x39]
     *     3rd byte: [0x81, 0xFE]
     *     4th byte: [0x30, 0x39]
     * *** this implementation doesn't support 4 byte characters.
     */
    assert (idx <= strlen(text));

    if (idx == 0 || idx == strlen(text)) {
        /*  If this is first or \0.
         */
        return false;
    }

    /* Search backwards to find the closest non leading character.
     * Then, this character is :
     *  1. the trailing byte of a 2 byte character, OR
     *  2. ASCII, OR
     *  3. Unknow character
     *  In all cases, the next byte is the start of next character.
     */
    
    int i;
    for (i = idx -1; i >= 0; i--) {
        if (!IS_LEADING_CHAR(text[i])) {
            break;
        }
    }
    
    /* Then search forwards, get characters one by one, until the
     * next start >= idx.
     * if next start == idx, then, not inside
     * if next start > idx, inside
     */
    unsigned int nextchar;
    if (i != 0) {
        nextchar = i+1;
    } else {
        // all are leading character's range, so starting from 0
        nextchar = 0;
    }

    while (nextchar < idx) {
        int len;
        unsigned long c;
        nextchar = (unsigned int )GetMultiByteChar(text, nextchar, &c, &len);
    }

    if (nextchar == idx || nextchar == (unsigned int)-1) {
        return false;
    } 
    assert (nextchar > idx);
    return true;
}
